package futbol.tacticas.concursantes.laponiafc;

import java.awt.Color;
import java.awt.Point;
import java.awt.geom.Point2D;
import java.util.Map;
import java.util.Vector;

import futbol.General;
import futbol.tacticas.Comando;
import futbol.tacticas.ComandoGolpearBola;
import futbol.tacticas.ComandoIrA;
import futbol.tacticas.SituacionJugadores;
import futbol.tacticas.concursantes.Tactica;
import futbol.tacticas.concursantes.laponiafc.ResultadoIntercepcion.GOL;

enum Rol {
	ATACANTE, MEDIO, DEFENSA, PORTERO
}

enum Equipo {
	PROPIO, CONTRARIO, CUALQUIERA
}

/**
 * 
 * @author Galderic Punti
 * 
 * @date 06-06-2008
 * 
 */
public class LaponiaFC implements Tactica {

	public final static String NOMBRE = "LaponiaFC";

	public final static int VELOCIDAD_BOLA_MINIMA = 15;
	public final static Point POSTE_IZQUIERDO = new Point(160, -30);
	public final static Point POSTE_DERECHO = new Point(160, 30);
	public final static Point PUNTO_PENALTY = new Point(100, 0);
	public final static Point PORTERIA = new Point(140, 0);
	public final static int NUM_JUGADORES = 11;
	public final static double ANGULO_ERROR = Math.PI / 5;

	// parametros de configuracion
	final int MAX_POSICIONES_FUTURAS = 20;
	final int RADIO_RED = 60;
	final int DETALLE_RED = 6;
	final int DISTANCIA_INTERCEPCION = 10;
	final int ARC_STEPS = 10;
	final int MEDIOS_SUBEN = 30;

	final Jugador[] jugadores = new Jugador[] {
			new Jugador(0, 3, Rol.PORTERO, -159, 0), // 1
			new Jugador(1, 5, Rol.DEFENSA, -105, -60), // 2
			new Jugador(2, 4, Rol.DEFENSA, -105, 0), // 3
			new Jugador(3, 5, Rol.DEFENSA, -105, 60), // 4
			new Jugador(4, 4, Rol.DEFENSA, -70, 0), // 5
			new Jugador(5, 4, Rol.MEDIO, -20, -35), // 6
			new Jugador(6, 4, Rol.MEDIO, -20, 35), // 7
			new Jugador(7, 4, Rol.MEDIO, -20, 0), // 8
			new Jugador(8, 3, Rol.ATACANTE, 60, -60), // 9
			new Jugador(9, 3, Rol.ATACANTE, 60, 60), // 10
			new Jugador(10, 3, Rol.ATACANTE, 60, 0), // 11

			new Jugador(11, 3, Rol.ATACANTE, 0, 0),// 1
			new Jugador(12, 3, Rol.ATACANTE, 0, 0), // 2
			new Jugador(13, 3, Rol.ATACANTE, 0, 0), // 3
			new Jugador(14, 3, Rol.ATACANTE, 0, 0), // 4
			new Jugador(15, 3, Rol.ATACANTE, 0, 0), // 5
			new Jugador(16, 3, Rol.ATACANTE, 0, 0), // 6
			new Jugador(17, 3, Rol.ATACANTE, 0, 0), // 7
			new Jugador(18, 3, Rol.ATACANTE, 0, 0), // 8
			new Jugador(19, 3, Rol.ATACANTE, 0, 0), // 9
			new Jugador(20, 3, Rol.ATACANTE, 0, 0), // 10
			new Jugador(21, 3, Rol.ATACANTE, 0, 0) }; // 11

	// objetos reutilizados
	final MovimientoBola currBallMov = new MovimientoBola(0, 0, 0, 0);
	final Point2D.Double ball_i = new Point2D.Double();
	final Point2D.Double defenseTarget = new Point2D.Double();
	final Heuristico heuristicoAtaque = new Heuristico();
	final Heuristico coverHeuristic = new Heuristico();

	// estado
	Equipo ultimoGol = Equipo.CONTRARIO;
	ResultadoIntercepcion intcpResult = new ResultadoIntercepcion();
	public int golesAFavor = 0;
	public int golesEnContra = 0;
	public double nPosicionBolaParaEmpate;
	public int ticks;

	// utiles
	final Util help = Util.getInstance();
	Vector<Comando> resultado = new Vector<Comando>();

	public LaponiaFC() {
	}

	public Color getColor1() {
		return Color.YELLOW;
	}

	public Color getColor2() {
		return Color.YELLOW;
	}

	public Color getColorNumero() {
		return Color.BLACK;
	}

	protected final void moverJugador(Jugador p, double x, double y,
			Vector<Comando> resultado) {

		if (x > 160) {
			x = 160;
		} else if (x < -160) {
			x = -160;
		}

		if (y > 120) {
			y = 120;
		} else if (y < -120) {
			y = -120;
		}

		resultado.add(new ComandoIrA(p.id, x, y));
		p.idle = false;
	}

	protected final void chutarAGol(Jugador p, Vector<Comando> resultado) {

		double dx = 160 - p.vm.cur.x;
		double dy = 0 - p.vm.cur.y;

		double tx = p.vm.cur.x + dx * 40;
		double ty = p.vm.cur.y + dy * 40;

		resultado.add(new ComandoGolpearBola(p.id, tx, ty));
		p.idle = false;
	}

	public final Vector<Comando> getComandos(SituacionJugadores sj) {

		resultado.clear();

		intcpResult = new ResultadoIntercepcion();

		obtenerInformacion(sj);

		atacar(sj, resultado, currBallMov);

		defender(sj, resultado);

		recuperarPosiciones(sj);

		return resultado;
	}

	public final int getFuerza(int n) {
		return 10 - jugadores[n].getSpeed();
	}

	public final String getNombre() {
		return NOMBRE;
	}

	public final int getXInicial(int n) {

		comprobarGoles(new Point(0, 0), false);

		return (int) jugadores[n].prefered.x;
	}

	public final int getYInicial(int n) {

		comprobarGoles(new Point(0, 0), false);

		return (int) jugadores[n].prefered.y;
	}

	private final void comprobarGoles(Point bola, boolean update) {

		ultimoGol = Equipo.CUALQUIERA;

		if (currBallMov.cur.distance(bola) > 100) {
			if (currBallMov.cur.x < 0) {
				this.ultimoGol = Equipo.CONTRARIO;
				if (update) {
					golesEnContra++;
					currBallMov.ultimoContrarioAGolpear.goles++;
					currBallMov.reset();
				}
			} else {
				this.ultimoGol = Equipo.PROPIO;
				if (update) {
					golesAFavor++;
					currBallMov.reset();
				}
			}
		}
	}

	private final void obtenerInformacion(SituacionJugadores sj) {

		final Point bola = sj.getBola();

		nPosicionBolaParaEmpate += bola.getX() / 10;
		ticks++;

		comprobarGoles(bola, true);

		for (int i = 0; i < jugadores.length; i++) {
			Jugador p = jugadores[i];

			p.marcadores = 0;

			Point ppos;

			if (i < NUM_JUGADORES) {
				ppos = sj.getMisJugadores()[i];
				p.idle = true;
			} else {
				ppos = sj.getContrario()[i - NUM_JUGADORES];
			}

			p.vm.addPoint(ppos.x, ppos.y, jugadores);

			final int velocidad = (int) p.vm.getSpeed();
			if ((velocidad > p.getSpeed()) && (velocidad <= 6)
					&& (i >= NUM_JUGADORES)
					&& (Equipo.CUALQUIERA.equals(ultimoGol))) {
				p.setSpeed(velocidad);
			}

			p.distanceToBall = bola.distance(ppos);
		}

		currBallMov.addPoint(sj.getBola().x, sj.getBola().y, jugadores);

		this.intcpResult = intercept();

	}

	private final void atacar(SituacionJugadores sj, Vector<Comando> resultado,
			Movimiento ballMov) {

		final Point bola = sj.getBola();

		if ((golesAFavor <= golesEnContra )
				&& (nPosicionBolaParaEmpate < 500) && (ticks > 600)) {
			moverJugador(jugadores[7], defenseTarget.x, defenseTarget.y,
					resultado);
		}

		if (intcpResult != null) {
			defenseTarget.setLocation(intcpResult.getPoint());
		} else {
			defenseTarget.setLocation(sj.getBola());
		}

		Jugador d = help.jugadorMasCercanoAPunto(jugadores, defenseTarget.x,
				defenseTarget.y, Equipo.PROPIO, true);

		if (d != null) {
			moverJugador(d, defenseTarget.x, defenseTarget.y, resultado);
		}

		for (Jugador p : intcpResult.getPlayers()) {

			if (p.id >= NUM_JUGADORES) {
				continue;
			}

			if (intcpResult.getTurn() <= 0) {

				final Point2D.Double tpos = new Point2D.Double();
				final Point2D.Double ppos = p.vm.cur;

				final double goalProb = goalProbability(p, ballMov.cur);

				Point btarget = new Point();

				Heuristico bestHeuristic = new Heuristico();

				double bh = -Double.MAX_VALUE;

				for (int dx = -RADIO_RED; dx < RADIO_RED; dx += DETALLE_RED) {

					for (int dy = -RADIO_RED; dy < RADIO_RED; dy += DETALLE_RED) {

						tpos.setLocation(ppos.x + dx, ppos.y + dy);

						if ((tpos.y < -120) || (tpos.y > 120)
								|| (tpos.x < -160)) {
							continue;
						}

						heuristicoAtaque(p, tpos, bola, heuristicoAtaque);

						final double ihValue = heuristicoAtaque.getValor();

						if (ihValue > bh) {
							bh = ihValue;
							btarget.setLocation(tpos.x, tpos.y);
							bestHeuristic = heuristicoAtaque.clone();
						}
					}
				}

				if ((goalProb >= .9)
						|| ((goalProb > .65f) && (bestHeuristic.getConfianza() < .99))) {
					chutarAGol(p, resultado);
				} else {

					resultado.add(new futbol.tacticas.ComandoGolpearBola(p.id,
							btarget.x, btarget.y));

					// vamos a recoger el pase
					for (Map.Entry<Jugador, Point2D.Double> e : bestHeuristic.intercepciones
							.entrySet()) {
						if (e.getKey().id < NUM_JUGADORES) {
							moverJugador(e.getKey(), e.getValue().x, e
									.getValue().y, resultado);
						}
					}

				}

			} else {
				moverJugador(p, intcpResult.getPoint().x, intcpResult
						.getPoint().y, resultado);
			}
		}

		double med_x = -160;
		double def_x = -160;
		for (int i = 0; i < NUM_JUGADORES; i++) {
			final Jugador p = jugadores[i];
			if (Rol.MEDIO.equals(p.rol)) {
				if (p.vm.cur.x > med_x) {
					med_x = p.vm.cur.x;
				}
			} else if (Rol.DEFENSA.equals(p.rol)) {
				if (p.vm.cur.x > def_x) {
					def_x = p.vm.cur.x;
				}
			}
		}

		for (int i = 0; i < NUM_JUGADORES; i++) {
			Jugador p = jugadores[i];
			if ((p.idle)
					&& (Rol.ATACANTE.equals(p.rol) || (Rol.MEDIO.equals(p.rol) && bola.x > MEDIOS_SUBEN))) {

				double target_x = p.vm.cur.x;
				double target_y = p.prefered.y;

				if (Rol.MEDIO.equals(p.rol)) {
					target_x = def_x + 55;
				} else if (Rol.ATACANTE.equals(p.rol)) {
					target_x = med_x + 55;
				} else {
					target_x = p.prefered.x;
				}

				// los delanteros no tiene sentido que avancen tanto
				if ((target_x > 80) && (Rol.ATACANTE.equals(p.rol))) {
					target_x = 80;
				}

				if ((target_x > 30) && (Rol.MEDIO.equals(p.rol))) {
					target_x = 30;
				}

				moverJugador(p, target_x, target_y, resultado);
			}
		}

	}

	private final void recuperarPosiciones(SituacionJugadores sj) {

		for (int i = 0; i < NUM_JUGADORES; i++) {
			Jugador p = jugadores[i];

			if (p.idle) {
				moverJugador(p, p.prefered.x, p.prefered.y, resultado);
			}

		}
	}

	private final void defender(SituacionJugadores sj, Vector<Comando> result) {

		for (int i = 0; i < NUM_JUGADORES; i++) {
			final Jugador pi = jugadores[i];

			if (pi.idle) {

				Jugador aCubrir = null;
				double bestHeuristic = -Double.MAX_VALUE;

				for (int j = NUM_JUGADORES; j < jugadores.length; j++) {
					final Jugador pj = jugadores[j];

					heuristicoDefensa(pi, pj, coverHeuristic);

					final double ihValue = coverHeuristic.getValor();
					if ((ihValue > bestHeuristic) && (ihValue > 0)) {
						bestHeuristic = ihValue;
						aCubrir = pj;
					}
				}
				if (aCubrir != null) {
					moverJugador(pi, aCubrir.vm.cur.x, aCubrir.vm.cur.y, result);
					aCubrir.marcadores++;
				}
			}
		}

	}

	protected final ResultadoIntercepcion intercept() {

		return intercept(currBallMov, 0);
	}

	private final ResultadoIntercepcion intercept(MovimientoBola ballMov,
			int from) {

		ResultadoIntercepcion result = new ResultadoIntercepcion();

		ball_i.x = ballMov.cur.x;
		ball_i.y = ballMov.cur.y;

		// vi es la distancia que recorrera en el proxim moviment (detectada
		// menys 1)
		int vi = ballMov.getSpeed() - 1;

		double s = 0;

		if (vi < 0) {
			vi = 0;
		}

		double angulo = ballMov.getAngle();

		// moviment
		int i = 0;

		while ((i < MAX_POSICIONES_FUTURAS) && result.isEmpty()
				&& GOL.NA.equals(result.gol)) {

			if ((vi < VELOCIDAD_BOLA_MINIMA) && (i >= from)) {
				// entramos a considerar si alguien puede interceptar esto

				ball_i.x = ballMov.cur.x + Math.cos(angulo) * s;
				ball_i.y = ballMov.cur.y + Math.sin(angulo) * s;

				result.gol = fixBall(ball_i);

				if (!GOL.NA.equals(result.gol)) {
					return result;
				}

				if (vi == 0) {

					Jugador p = help.jugadorMasCercanoAPunto(jugadores,
							ball_i.x, ball_i.y, Equipo.PROPIO, false);

					double d = p.vm.cur.distance(ball_i.x, ball_i.y);

					result.addPlayer(p);
					result.setTurn((int) Math.ceil((d - DISTANCIA_INTERCEPCION)
							/ p.getSpeed()));
					result.getPoint().setLocation(ball_i.x, ball_i.y);

					return result;
				}

				for (int k = 0; k < jugadores.length; k++) {

					Jugador p = jugadores[k];

					final Point2D.Double ppos = p.vm.cur;
					final int v = p.getSpeed();

					final double d = ppos.distance(ball_i.x, ball_i.y);

					final boolean intcp = (d < v * i + DISTANCIA_INTERCEPCION);

					if (intcp) {
						result.addPlayer(p);
						result.setTurn(i);
						result.getPoint().setLocation(ball_i.x, ball_i.y);
						if (p.id < NUM_JUGADORES) {
							result.ownIntercepts++;
						} else {
							result.enemyIntercepts++;
						}
					}
				}
			}

			s += vi;

			vi = vi - 1;

			if (vi < 0) {
				vi = 0;
			}

			i++;
		}

		return result;
	}

	private final GOL fixBall(Point2D.Double p) {

		if (p.x >= 160 && p.y > -30 && p.y < 30) {
			return GOL.AFAVOR;
		} else if (p.x <= -160 && p.y > -30 && p.y < 30) {
			return GOL.CONTRARIO;
		}

		if (p.x > 160) {
			p.x = 320 - p.x;
		} else if (p.x < -160) {
			p.x = -320 - p.x;
		}

		if (p.y > 120) {
			p.y = 240 - p.y;
		} else if (p.y < -120) {
			p.y = -240 - p.y;
		}

		return GOL.NA;
	}

	protected final double goalProbability(Jugador p) {
		return goalProbability(p, p.vm.cur);
	}

	protected final double goalProbability(Jugador p, Point2D.Double pos) {

		double kicker_x = pos.x;

		if (p.id >= NUM_JUGADORES) {
			kicker_x = -kicker_x;
		}

		final double a1 = General.getAngulo(kicker_x, pos.y, POSTE_IZQUIERDO.x,
				POSTE_IZQUIERDO.y);
		final double a2 = General.getAngulo(kicker_x, pos.y, POSTE_DERECHO.x,
				POSTE_DERECHO.y);

		// kick angle
		double result = 0.0f;

		double diff = Math.abs(a1 - a2);

		if (diff > Math.PI) {
			diff = 2 * Math.PI - diff;
		}

		result = diff / (LaponiaFC.ANGULO_ERROR);

		double dx = 160 - kicker_x;
		double dy = 0 - pos.y;

		double tx = kicker_x + dx * 40;
		double ty = pos.y + dy * 40;

		final MovimientoBola kick = new MovimientoBola(kicker_x, pos.y, tx, ty);

		kick.setSpeed(3 * (10 - p.getSpeed()) + 1);

		ResultadoIntercepcion intcp = intercept(kick, 1);
		for (Jugador pi : intcp.getPlayers()) {
			if (((pi.id >= NUM_JUGADORES) && (p.id < NUM_JUGADORES))
					|| ((pi.id < NUM_JUGADORES) && (p.id >= NUM_JUGADORES))) {
				result = 0;
			}
		}

		if (GOL.NA.equals(intcp.gol)) {
			result = 0;
		}

		return Math.min(1.0, result);
	}

	protected final void heuristicoDefensa(Jugador p, Jugador t, Heuristico heu) {

		heu.clear();

		if (t.marcadores == 0) {
			heu.addMessage("x no tener marcajes:20", 20);
		}

		final double d0 = p.vm.cur.distance(t.vm.cur);
		double d = 20 / (d0 + 1);
		heu.addMessage("x distancia sumamos:" + d, d);

		// heu.addMessage("x gols:" + (t.goles), t.goles);

		double goalProbability = goalProbability(t);
		heu.addMessage("x prob. gol:" + goalProbability, goalProbability);

		if (t.vm.cur.x > 20) {
			heu.addMessage("x estar en su campo:-90", -90);
		}

		if (Rol.PORTERO.equals(p.rol)) {
			if (t.vm.cur.distance(-160, 0) > 35) {
				heu.addMessage("x salir a lo bonzo:-200", -200);
			}
		}

		double lejos = p.prefered.distance(t.vm.cur);
		heu.addMessage("x ser cerca de su lugar preferido:"
				+ (40 / (lejos + 1)), 40 / (lejos + 1));

	}

	protected final void heuristicoAtaque(Jugador p, Point2D.Double pos,
			Point bola, Heuristico heu) {

		final double inc = (Math.PI / 5) / ARC_STEPS;

		double[] intcps_ok = new double[ARC_STEPS];
		double[] intcps_ko = new double[ARC_STEPS];
		boolean[] intcps_done = new boolean[ARC_STEPS];

		boolean done = false;

		heu.clear();

		int enemyKickers = 0;

		for (int i = NUM_JUGADORES; i < jugadores.length; i++) {
			if (jugadores[i].distanceToBall < (DISTANCIA_INTERCEPCION + 2)) {
				enemyKickers++;
			}
		}

		final MovimientoBola golpeo = new MovimientoBola(p.vm.cur.x,
				p.vm.cur.y, pos.x, pos.y);

		final int force = p.fuerzaPase(pos);

		if (force <= 0) {
			heu.addMessage("no hariamos nada", -500);
			return;
		}

		golpeo.setSpeed(3 * force);

		double d = golpeo.getSpeed();

		double vi = golpeo.getSpeed() - 1;

		double t = 1;

		final Point2D.Double ball_i = new Point2D.Double();
		double angulo = golpeo.getAngle();

		if (Rol.PORTERO.equals(p.rol)
				&& (golpeo.getSpeed() >= (10 - p.getSpeed()) * 3)
				&& ((golpeo.getAngle() < .200) || (golpeo.getAngle() > 6.000))) {
			heu.addMessage("despeja portero", 40);
		}

		while (!done) {
			if (vi < VELOCIDAD_BOLA_MINIMA) {

				for (int i = 0; i < ARC_STEPS; i++) {
					if (!intcps_done[i]) {
						final double angle = angulo - Math.PI / 10 + inc * i;

						ball_i.x = bola.x + Math.cos(angle) * d;
						ball_i.y = bola.y + Math.sin(angle) * d;

						GOL gol = fixBall(ball_i);

						if (GOL.AFAVOR.equals(gol)) {
							heu.addMessage("gol a favor", 1);
							intcps_done[i] = true;
						} else if (GOL.CONTRARIO.equals(gol)) {
							heu.addMessage("gol en contra", -20);
							intcps_done[i] = true;
						}

						for (Jugador pi : jugadores) {
							double di = pi.vm.cur.distance(ball_i.x, ball_i.y);
							if (di <= pi.getSpeed() * t
									+ DISTANCIA_INTERCEPCION) {

								heu.intercepciones.put(pi, new Point2D.Double(
										ball_i.x, ball_i.y));
								if (pi.id < NUM_JUGADORES) {
									intcps_ok[i] += 1;
								} else {
									intcps_ko[i] += 1;
								}
								intcps_done[i] = true;
							}
						}
					}
				}

				done = true;

				for (int i = 0; i < ARC_STEPS; i++) {
					done = done && intcps_done[i];
				}
			}

			d += vi;

			if (vi > 0) {
				vi--;
			}

			t++;
		}

		double confianza = 0.0;
		for (int i = 0; i < ARC_STEPS; i++) {
			confianza += Math.min(1, (intcps_ok[i]) / (intcps_ko[i] + 1))
					/ (double) ARC_STEPS;
		}

		double mx = 0;
		double my = 0;
		int size = heu.intercepciones.entrySet().size();

		for (Map.Entry<Jugador, Point2D.Double> e : heu.intercepciones
				.entrySet()) {
			mx += e.getValue().x;
			my += e.getValue().y;
		}

		if (size > 0) {
			mx /= heu.intercepciones.entrySet().size();
			my /= heu.intercepciones.entrySet().size();
		}

		Point2D.Double media = new Point2D.Double(mx, my);

		Point2D target;

		if (p.getSpeed() >= 5) {
			target = new Point2D.Double(160, 0);
		} else {
			target = PUNTO_PENALTY;
		}

		// tiramos hacia adelante, interceptando si es posible
		double d1 = currBallMov.cur.distance(target);
		double d2 = media.distance(target);

		double e = (float) (d1 - d2) / 2;
		e *= confianza;
		heu.addMessage("x adelantar de manera segura:" + e, e);

		heu.setConfianza(confianza);

		if (p.getSpeed() > 4) {
			for (Map.Entry<Jugador, Point2D.Double> entry : heu.intercepciones
					.entrySet()) {
				Jugador j = entry.getKey();
				if ((j.id < NUM_JUGADORES)
						&& (j.getSpeed() < p.getSpeed() && !Rol.PORTERO
								.equals(j.rol))) {
					heu.addMessage("recuperacion pelota", 50 * confianza);
				}
			}
		}

		// no tiramos cerca de nuestro punto penalty
		double danger = media.distance(-90, 0);
		heu.addMessage("x poner en apuros al portero", -80 / (danger + 1));

		// despejamos
		if ((force >= 6) && (media.x > p.vm.cur.x) && (p.vm.cur.x <= -90)) {
			heu.addMessage("x despejar:" + (force - 5), 2 * (force - 5));
		}

		if (heu.intercepciones.size() == 1) {
			if (heu.intercepciones.keySet().iterator().next().id == p.id) {
				if (force > 3) {
					heu.addMessage("autopase demasiado rapido", -50);
				}

				if ((bola.x > 25)
						&& (p.rol.equals(Rol.ATACANTE) && (confianza > .98))) {
					heu.addMessage("avanza hacia porteria", 50);
				}
			}
		}

		if ((golpeo.getSpeed() < 15) && (enemyKickers > 0)) {
			heu.addMessage("golpeo demaisado flojo" + (-enemyKickers * 80),
					-enemyKickers * 80);
		}
	}
}
